#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>

#include "imbus_internal.h"
#include "im_connection.h"
#include "im_byte_stream.h"
#include "im_object_queue.h"
#include "im_connection_unix.h"

/************************** Declaration of internal functions ***************/
static void     __im_connection_initialize  (IMPointer       connection);
static void     __im_connection_finalize    (IMPointer       connection);
static void     __im_connection_copy        (IMPointer       dest,
                                             IMConstPointer  src);
static IMBool   __im_connection_serialize   (IMConstPointer  obj,
                                             IMPointer       stream);
static IMBool   __im_connection_deserialize (IMPointer       obj,
                                             IMPointer       stream);
static void     __im_connection_class_init  (IMPointer       klass);

static IMBool   __im_connection_read        (IMConnection   *connection,
                                             IMInt          *timeout,
                                             IMIOError      *error);

/****************** Declaration of object and class structure ************/
struct _IMConnection
{
    IMInitiallyOwned parent_object;

    IMIOChannel   *channel;
    IMObjectQueue *queue;
    IMStream      *input;
    IMStream      *output;
};

struct _IMConnectionClass
{
    IMInitiallyOwnedClass parent_class;
};

/************* Implementation of internal functions ***************/
static void
__im_connection_copy (IMPointer      dest,
                      IMConstPointer src)
{
    _im_assert_not_reached("IMConnection objects couldn't be copied.\n");
}

static IMBool
__im_connection_serialize (IMConstPointer obj,
                           IMPointer      stream)
{
    _im_assert_not_reached("IMConnection objects couldn't be serialized.\n");
    return FALSE;
}

static IMBool
__im_connection_deserialize (IMPointer      obj,
                             IMPointer      stream)
{
    _im_assert_not_reached("IMConnection objects couldn't be deserialized.\n");
    return FALSE;
}

static void
__im_connection_class_init (IMPointer klass)
{
    IMObjectClass *ocp = (IMObjectClass *)klass;

    ocp->copy        = __im_connection_copy;
    ocp->serialize   = __im_connection_serialize;
    ocp->deserialize = __im_connection_deserialize;
}

static void
__im_connection_initialize (IMPointer obj)
{
    IMConnection *p = IM_CONNECTION(obj);
    _im_assert (p);
    p->channel = 0;
    p->queue   = im_object_ref (im_object_queue_new ());
    p->input   = im_object_ref (im_byte_stream_new ());
    p->output  = im_object_ref (im_byte_stream_new ());
}

static void
__im_connection_finalize (IMPointer obj)
{
    IMConnection *p = IM_CONNECTION(obj);
    _im_assert (p);
    if (p->channel) im_object_unref (p->channel);
    im_object_unref (p->queue);
    im_object_unref (p->input);
    im_object_unref (p->output);
}

void
__im_type_register_connection ()
{
    static IMTypeInfo im_connection_type_info =
    {
        sizeof (IMConnectionClass), /**< class_size >**/
        __im_connection_class_init, /**< class_init >**/
        0,                          /**< class_finalize >**/

        sizeof (IMConnection),      /**< instance_size >**/
        __im_connection_initialize, /**< instance_init >**/
        __im_connection_finalize,   /**< instance_finalize >**/
    };

    __im_type_register_builtin_class (IM_TYPE_INITIALLY_OWNED,
                                      IM_TYPE_CONNECTION,
                                      "Connection",
                                      &im_connection_type_info,
                                      TRUE);
}

static IMBool
__im_connection_read (IMConnection *connection,
                      IMInt        *timeout,
                      IMIOError    *error)
{
    IMInt result = 0;
    IMType type;
    if (!connection->channel || !im_io_channel_is_readable (connection->channel)) {
        if (error) *error = IM_IO_ERROR_BADF;
        return FALSE;
    }
    im_stream_clear (connection->input);
    if (im_stream_read (connection->input, connection->channel, timeout, error)) {
        type = im_stream_get_data_type (connection->input);
        while (IM_TYPE_IS_OBJECT(type)) {
            IMPointer object = im_object_new (type);
            if (im_stream_get_object (connection->input, object)) {
                im_object_queue_push_back (connection->queue, object);
                result ++;
            } else {
                im_object_unref (object);
                im_stream_skip_data (connection->input);
            }
            type = im_stream_get_data_type (connection->input);
        }
    }
    return result > 0;
}

IMConnection*
im_connection_new (IMIOChannel *channel)
{
    IMConnection *connection;
    _im_return_val_if_fail (IM_IS_IO_CHANNEL(channel), 0);
    connection = (IMConnection *) im_object_new (IM_TYPE_CONNECTION);
    _im_assert (connection);
    connection->channel = channel;
    im_object_ref (channel);
    im_io_channel_set_close_on_unref (channel, TRUE);
    return connection;
}

void
im_connection_close (IMConnection *connection)
{
    _im_return_if_fail (IM_IS_CONNECTION(connection));
    if (connection->channel) {
        im_io_channel_close (connection->channel);
        im_object_unref (connection->channel);
        connection->channel = 0;
    }
}

IMBool
im_connection_is_connected (const IMConnection *connection)
{
    _im_return_val_if_fail (IM_IS_CONNECTION(connection), FALSE);
    return connection->channel && im_io_channel_valid (connection->channel);
}

IMIOChannel*
im_connection_get_io_channel (const IMConnection *connection)
{
    _im_return_val_if_fail (IM_IS_CONNECTION(connection), 0);
    return connection->channel;
}

IMBool
im_connection_set_io_channel (IMConnection *connection, IMIOChannel *channel)
{
    _im_return_val_if_fail (IM_IS_CONNECTION(connection), FALSE);
    _im_return_val_if_fail (IM_IS_IO_CHANNEL(channel), FALSE);
    _im_return_val_if_fail (im_io_channel_valid (channel), FALSE);
    if (connection->channel)
        im_object_unref (connection->channel);
    connection->channel = channel;
    im_object_ref (channel);
    im_io_channel_set_close_on_unref (channel, TRUE);
    return TRUE;
}

IMInt
im_connection_pending (IMConnection *connection, IMIOError *error)
{
    IMSize num;
    IMInt timeout = 0;
    IMIOError local_error = IM_IO_ERROR_NONE;
    _im_return_val_if_fail (IM_IS_CONNECTION(connection), 0);
    if (error) *error = IM_IO_ERROR_NONE;

    num = im_object_queue_length (connection->queue);
    if (num > 0) return num;

    if (__im_connection_read (connection, &timeout, &local_error))
        return im_object_queue_length (connection->queue);
    if (error) *error = local_error;
    if (local_error == IM_IO_ERROR_NONE || local_error == IM_IO_ERROR_AGAIN)
        return 0;
    return -1;
}

IMPointer
im_connection_next (IMConnection *connection,
                    IMInt        *timeout,
                    IMIOError    *error)
{
    IMInt def_timeout = -1;
    IMPointer object;
    _im_return_val_if_fail (IM_IS_CONNECTION(connection), 0);
    if (!timeout) timeout = &def_timeout;
    if (error) *error = IM_IO_ERROR_NONE;
    if (!im_object_queue_length (connection->queue)) {
        if (!__im_connection_read (connection, timeout, error))
            return 0;
    }

    object = im_object_queue_front (connection->queue);
    im_object_ref (object);
    im_object_queue_pop_front (connection->queue);
    return object;
}

IMBool
im_connection_send (IMConnection    *connection,
                    IMConstPointer   object,
                    IMInt           *timeout,
                    IMIOError       *error)
{
    IMInt def_timeout = -1;
    IMBool ret;
    _im_return_val_if_fail (IM_IS_CONNECTION(connection), FALSE);
    _im_return_val_if_fail (IM_IS_OBJECT(object), FALSE);
    if (!timeout) timeout = &def_timeout;
    if (error) *error = IM_IO_ERROR_NONE;
    im_stream_put_object (connection->output, object);
    ret = im_stream_write (connection->output, connection->channel, timeout, error);
    if (ret) im_stream_clear (connection->output);
    return ret;
}

IMConnection*
im_connection_new_connect (const IMChar *address, IMIOError *error)
{
    return im_connection_unix_new_connect (address, error);
}

/*
vi:ts=4:nowrap:ai:expandtab
*/
